package com.enation.app.javashop.core.goodssearch.model;

import com.enation.app.javashop.core.goods.model.dos.CategoryDO;
import com.enation.app.javashop.core.goodssearch.enums.SortTypeEnum;
import com.enation.app.javashop.core.goodssearch.util.HexUtil;
import com.enation.app.javashop.framework.elasticsearch.EsConfig;
import com.enation.app.javashop.framework.elasticsearch.EsSettings;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.util.StringUtil;
import lombok.Data;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;

/**
 * @author JFeng
 * @date 2020/2/16 16:11
 */
@Data
public class LocalQueryBuilder {

    private Integer maxDistance = 1000;

    private String keyWord;

    private String sortType;

    private GeoPoint originPoint;

    private GoodsSearchDTO goodsSearchDTO;

    private SearchRequestBuilder searchRequestBuilder;

    private BoolQueryBuilder boolQueryBuilder;

    private  FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders;

    private GeoDistanceQueryBuilder distanceQueryBuilder;

    private GeoDistanceSortBuilder distanceSortBuilder;

    private ScoreSortBuilder scoreSortBuilder;


    public LocalQueryBuilder(ElasticsearchTemplate elasticsearchTemplate,
                             EsConfig esConfig,
                             GoodsSearchDTO goodsSearchDTO) {
        // this.maxDisatce = maxDisatce;
        this.originPoint = new GeoPoint(goodsSearchDTO.getUserLat(), goodsSearchDTO.getUserLng());
        this.sortType = goodsSearchDTO.getSort();
        this.keyWord = goodsSearchDTO.getKeyword();
        this.goodsSearchDTO = goodsSearchDTO;
        this.searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch(esConfig.getIndexName() + "_" + EsSettings.GOODS_INDEX_NAME);
    }


    public LocalQueryBuilder boolQuery() {
        boolQueryBuilder = QueryBuilders.boolQuery();

        //商城搜索(自提或者同城配送商品)
        QueryBuilder orQuery = QueryBuilders.boolQuery()
                .should(QueryBuilders.termQuery("isLocal", "1"))
                .should(QueryBuilders.termQuery("isSelfTake", "1"));
        boolQueryBuilder.must(orQuery);
        // 关键字检索
        if (!StringUtil.isEmpty(keyWord)) {
            QueryStringQueryBuilder queryString = new QueryStringQueryBuilder(keyWord).field("goodsName");
            queryString.defaultOperator(Operator.AND);
            queryString.analyzer("ik_smart");
            boolQueryBuilder.must(queryString);
        }
        // 分类检索
        if (goodsSearchDTO.getCategoryPath() != null) {
            boolQueryBuilder.must(QueryBuilders.wildcardQuery("categoryPath", HexUtil.encode(goodsSearchDTO.getCategoryPath()) + "*"));
        }
        // 删除的商品不显示
        boolQueryBuilder.must(QueryBuilders.termQuery("disabled", "1"));
        // 未上架的商品不显示
        boolQueryBuilder.must(QueryBuilders.termQuery("marketEnable", "1"));
        // 待审核和审核不通过的商品不显示
        boolQueryBuilder.must(QueryBuilders.termQuery("isAuth", "1"));

        return this;
    }

    public LocalQueryBuilder functionScore() {
        //偏移量（offset）：与原点相差在偏移量之内的值也可以得到满分
        String offset = "3km";
        //衰减规模（scale）：当值超出了原点到偏移量这段范围，它所得的分数就开始进行衰减了，衰减规模决定了这个分数衰减速度的快慢
        String scale = "6km";
        //衰减值（decay）：该字段可以被接受的值（默认为 0.5），相当于一个分界点，具体的效果与衰减的模式有关
        double decay = 0.5;
        filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[1];
        //高斯函数
        GaussDecayFunctionBuilder geoBuilder = ScoreFunctionBuilders.gaussDecayFunction("location", originPoint, scale, offset, decay);
        FunctionScoreQueryBuilder.FilterFunctionBuilder geo = new FunctionScoreQueryBuilder.FilterFunctionBuilder(geoBuilder);
        filterFunctionBuilders[0] = geo;
        //以 e 为底的指数函数
        // FieldValueFactorFunctionBuilder generalBuilder = ScoreFunctionBuilders.fieldValueFactorFunction("grade").factor(0.1f).modifier(FieldValueFactorFunction.Modifier.LN2P);
        // FunctionScoreQueryBuilder.FilterFunctionBuilder general = new FunctionScoreQueryBuilder.FilterFunctionBuilder(generalBuilder);
        // filterFunctionBuilders[1] = general;

        return this;
    }

    public LocalQueryBuilder postFilter() {
        // 以某点为中心，搜索指定范围
        distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
        //指定从哪个位置搜索
        distanceQueryBuilder.point(goodsSearchDTO.getUserLat(), goodsSearchDTO.getUserLng());
        //指定搜索多少km，distance可为自定义数值
        distanceQueryBuilder.distance(maxDistance, DistanceUnit.KILOMETERS);
        return this;
    }

    public LocalQueryBuilder distanceSort() {
        // 获取距离多少公里 这个才是获取点与点之间的距离的
        distanceSortBuilder = SortBuilders.geoDistanceSort("location", originPoint);
        distanceSortBuilder.unit(DistanceUnit.KILOMETERS);
        distanceSortBuilder.order(SortOrder.ASC);
        return this;
    }

    public LocalQueryBuilder scoreSort() {
        scoreSortBuilder = SortBuilders.scoreSort();
        return this;
    }

    public SearchRequestBuilder build() {
        // 综合排序
        if(sortType.startsWith(SortTypeEnum.GENERAL.name())){
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders
                    .functionScoreQuery(boolQueryBuilder, filterFunctionBuilders)
                    .boostMode(CombineFunction.MULTIPLY);
            searchRequestBuilder.setQuery(functionScoreQueryBuilder);
        }else{
            searchRequestBuilder.setQuery(boolQueryBuilder);
        }
        searchRequestBuilder.setPostFilter(distanceQueryBuilder);
        // 距离排序
        if (SortTypeEnum.DISTANCE.name().equals(sortType)) {
            searchRequestBuilder.addSort(distanceSortBuilder);
            searchRequestBuilder.addSort(scoreSortBuilder);
        } else {
            searchRequestBuilder.addSort(scoreSortBuilder);
            searchRequestBuilder.addSort(distanceSortBuilder);
        }
        return searchRequestBuilder;
    }


}
